﻿using System;
using System.Data;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Data.SqlTypes;
using System.Reflection;

using RAD.LightOne.Cache;
using RAD.LightOne.Data;
using System.Threading;

namespace RAD.LightOne
{
    #region Documentation
    /// <summary>
    /// Classe que possui métodos auxiliares às tarefas de persistência.
    /// Realiza pequenos ajustes e adequa valores. Realiza processamentos no/para o objeto intermediário.
    /// ThreadSafe
    /// </summary>
    #endregion Documentation
    internal static class InternalObjProcessor
    {
        #region Fields

        private static Mutex buildMutex;
        private static Mutex standardizeMutex;

        #endregion

        #region Constructors

        static InternalObjProcessor()
        {
            buildMutex = new Mutex();
            standardizeMutex = new Mutex();
        }

        #endregion Constructors

        #region Methods

        #region Documentation
        /// <summary>
        /// Este método recebe um DBItem e perecorre todas os DBTableItems contidos e seus DBFieldItems
        /// comparando e adequando os dados de acordo com o schema de cada tabela obtido, visando o menor número possível
        /// de falhas por tamanho ou valor em banco.
        /// </summary>
        /// <param name="item">item de banco a ser analizado.</param>
        #endregion Documentation
        public static void Standardize(InternalObj item)
        {
            foreach (Table tItem in item)
            {
                DataTable schema = SchemaHelper.GetTableSchema(tItem.Name);
                Standardize(schema, tItem);
            }

        }

        #region Documentation
        /// <summary>
        /// Padronização com captura transacioada do schema caso não em cache.
        /// </summary>
        #endregion
        public static void Standardize(InternalObj item, IDbTransaction trx)
        {
            foreach (Table tItem in item)
            {
                DataTable schema = SchemaHelper.GetTableSchema(trx, tItem.Name);
                Standardize(schema, tItem);
            }
        }

        #region Documentation
        /// <summary>
        /// Provê parte da funcionalidade dos demais Standardize, aqui efetua-se efetivamente
        /// a padronização da estrutura intermediária.
        /// </summary>
        #endregion Documentation
        private static void Standardize(DataTable schema, Table item)
        {
            try
            {
                standardizeMutex.WaitOne();

                foreach (Field fItem in item)
                {
                    try
                    {
                        DataColumn column = schema.Columns[fItem.Name];

                        if (!(fItem.AllowNull)) fItem.AllowNull = column.AllowDBNull;

                        if (!fItem.AllowNull && fItem.Value == null)
                        {
                            throw new System.Data.ConstraintException("The field " + fItem.Name + " cannot be null");
                        }
                        #region Comments
                        //realizar veirficações só é perinente caso exista um valor a ser verificado, caso o nulo seja permitido,
                        //o nulo será utilizado, entretanto não deve ser verificado.
                        #endregion
                        else if (fItem.Value != null)
                        {
                            #region Comments
                            //verificação de tamanho máximo só é pertinente em casos onde existe um tipo de valor string.
                            #endregion
                            if (fItem.Value is string)
                            {
                                if ((fItem.Lenght == -1) || (fItem.Lenght > -1 && fItem.Lenght > column.MaxLength))
                                {
                                    fItem.Lenght = column.MaxLength;
                                }
                                if (fItem.Lenght > -1)
                                {
                                    if (((string)fItem.Value).Length > fItem.Lenght)
                                    {
                                        fItem.Value = ((string)fItem.Value).Substring(0, fItem.Lenght);
                                    }
                                }
                            }
                            else if (fItem.Value is MyObject)
                            {
                                fItem.Value = ((MyObject)fItem.Value).Id.ToString();
                            }
                        }
                        else if (fItem.Value == null)
                        {
                            fItem.Value = DBNull.Value;
                        }
                    }
                    catch (ConstraintException constrExcept)
                    {
                        throw constrExcept;
                    }
                    catch (NullReferenceException nullRefExcept)
                    {
                        throw new NullReferenceException("InternalObjProcessor.Standardize().\nThe field '" + fItem.Name + "' don't exist in a database table '" + schema.TableName + "'.", nullRefExcept);
                    }
                    catch (Exception except)
                    {
                        throw new Exception("InternalObjProcessor.Standardize().\n" + except.Message, except);
                    }
                }
            }
            finally
            {
                standardizeMutex.ReleaseMutex();
            }
            
        }

        #region Documentation
        /// <summary>
        /// Função interna que verifica se um tipo é realmente igual ao tipo genérico
        /// passado.
        /// </summary>
        /// <typeparam name="T">tipo validador</typeparam>
        /// <param name="type">tipo verificado</param>
        /// <returns>true comparação positiva. false comparação negativa.</returns>
        #endregion Documentation
        private static bool IsIn<T>(Type type)
        {
            return (type == typeof(T));
        }

        #region Documentation
        /// <summary>
        /// Método que retorna um StringBuilder contendo uma query específica de "select" dos dados referentes a 
        /// objeto intermediário passado como parâmetro.
        /// </summary>
        /// <param name="structure">Objeto interno base para a contrução da expressão select.</param>
        /// <returns>stringBuilder contendo a expressão.</returns>
        #endregion
        public static StringBuilder BuildSelectQuery(InternalObj structure)
        {
            try
            {
                buildMutex.WaitOne();
                Configuration config = Configuration.Instance();
                #region Comments
                //instancio o cache e verifico se uma sql que atenda ao objeto interno passado já existe,
                //existindo, retorno-a.
                #endregion
                Cache.SelectQueryCache queryCache = SelectQueryCache.Instance();
                if (queryCache.Contains(structure.Type.FullName)) return queryCache[structure.Type.FullName];

                string qs = config.QuotePrefix;
                string qe = config.QuoteSuffix;

                StringBuilder strCache = new StringBuilder();
                StringBuilder sql = new StringBuilder("SELECT {FIELD} FROM {TABLE} WHERE {PRIMARY_CLAUSULE}{CLAUSULE}");
                for (int i = 0; i < structure.Count; i++)
                {
                    sql.Replace("{TABLE}", qs + structure[i].Name + qe + ((i < (structure.Count - 1)) ? ", {TABLE}" : "{TABLE}"));
                    if (i == 0) sql.Replace("{PRIMARY_CLAUSULE}", qs + structure[i].Name + qe + @"." + qs + structure[i][0] + qe + @"={0}");
                    if (structure.Count > 1)
                    {
                        switch (i)
                        {
                            case 0:
                                sql.Replace("{CLAUSULE}", @" AND " + qs + structure[i].Name + qe + @"." + qs + structure[i][0].Name + qe + @"={CLAUSULE}");
                                break;
                            case 1:
                                strCache.AppendFormat(@"{0}" + structure[i].Name + @"{1}.{0}{2}{1}", qs, qe,structure[i][0].Name);
                                sql.Replace("{CLAUSULE}", strCache.ToString() + "{CLAUSULE}");
                                break;
                            default:
                                sql.Replace("{CLAUSULE}", " AND " + strCache.ToString() + @"=" + qs + structure[i].Name + qe + @"." + qs + structure[i][0].Name + qe + @"{CLAUSULE}");
                                strCache = new StringBuilder(qs + structure[i].Name + qe + @"." + qs + @"id" + qe);
                                break;
                        }
                    }

                    //sendo a segunda tabela, os dois primeiros elementos serão as chaves id e class_name, e devem ser ignorados neste momento.
                    int init = i == 0 ? 0 : 2; 
                    for (int j = init; j < structure[i].Count; j++)
                    {
                        sql.Replace("{FIELD}", qs + structure[i].Name + qe + @"." + qs + structure[i][j].Name + qe + (((i == (structure.Count - 1)) && (j == (structure[i].Count - 1))) ? "{FIELD}" : ", {FIELD}"));
                    }
                }
                sql.Replace("{FIELD}", "");
                sql.Replace("{TABLE}", "");
                sql.Replace("{CLAUSULE}", "");
                //id da primeira tabela
                sql.Replace("{0}", structure[0][0].GetParameterName());

                DBEngine.EvalReplaceSchema(sql);

                //aqui adiciono o par de valor ao cache, o par contém o tipo e a sql gerada pelo processamento acima.
                lock (queryCache)
                {
                    queryCache.Add(new KeyValuePair<string, StringBuilder>(structure.Type.FullName, sql));
                }

                return sql;
            }
            finally
            {
                buildMutex.ReleaseMutex();
            }
        }


        #endregion Methods
    }
}
